package muse.probing;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.io.*;

public class ClientProbing implements IExecuteProbing{


	public InetAddress serverHost; // indirizzo server
	public int port; //porta di comunicazione
	public DatagramSocket socket;
	private boolean maxBandwithEstimated=false;
	private double omega =0.4; //parametro che indica l'accuratezza della stima
	private double DispersionMin=1.0;  //questa dispersione da un rate di 11,2 mbit/s
	private int DispersionMax=29; //questa dispersone da un rate di 0.5 mbit/s
	private int CurrentDispersion;
	
	public ClientProbing(InetAddress serverHost,int port) throws SocketException
	{
		this.serverHost = serverHost;
		this.port = port;
		socket = new DatagramSocket();
		socket.setSoTimeout(500);
	}
	
	public double executeMeasure(int count, int cnt, double AverageBW) {


		int packetPair=0;
		DispersionMax=29;
		DispersionMin=1.0;
		double banda=0;
		double errore =-1.0;
		try{
			while(maxBandwithEstimated==false){
				//creo un Datagramma da inviare al proxy indicandogli la dispersione ossia il temopo che intercorre tra 
				//l'invio di due pacchhetti UDP
				DatagramPacket pak = createPacket(DispersionMin,DispersionMax);
				DatagramPacket received = new DatagramPacket(new byte[1470],1470);

				int i=0;

				double dispersion=0;
				while(i!=3){
					socket.send(pak);

					//prima ricezione 
					socket.receive(received);
					long first= System.nanoTime();
					//seconda ricezione
					socket.receive(received);
					long second=System.nanoTime();
					double tempDispersion=(second-first)/(1000.0*1000.0);

					//tempDispersion e' la misura della dispersione in ricezione; filtro i valori di dispersione non consistenti
					if((tempDispersion -CurrentDispersion)>-0.2 &&(tempDispersion-CurrentDispersion)<2.0){
						//System.out.println("DISPERSION "+tempDispersion);
						dispersion+=tempDispersion;
					}
					else
					{
						dispersion+=CurrentDispersion+0.4;

					}
					i++;
				}
				dispersion=dispersion/i;
				//System.out.println("executeMeasure Dispersion in invio : "+CurrentDispersion+" dispersione in ricezione "+(float)dispersion+" errore tollerato "+errore);
				double relation=(CurrentDispersion-dispersion);
				//System.out.println("Relation "+relation +" errore tollerato "+errore);

				if(relation>errore)
					DispersionMax=CurrentDispersion;
				else
					DispersionMin=dispersion;



				//System.out.println("MAX "+DispersionMax+" MIN "+DispersionMin);
				//System.out.println("RESULt "+(DispersionMax-DispersionMin));
				if((DispersionMax-DispersionMin)<omega) // l'algoritmo iterativo termina nel momento in cui la Dispersione minima e massima differiscono di omega che rappresenta il livello di accuratezza ch si vuole raggiungere
					maxBandwithEstimated=true;
				errore+=0.2;

				++packetPair;
				//System.out.println("\n");

			}
		}catch(SocketTimeoutException timeout)
		{
			//se scatta il timeout il canale wireless risulta congestionato
			//occorre impostare un rate di trasmissione basso per evitare perdita di pacchetti
			System.out.println("ClientProbing executeMeasure: Congestione canale");
			DispersionMin=22;

		} 
		catch (IOException e) {

			e.printStackTrace();
		}
		banda=((1470.0*1000.0)/(DispersionMin*1024.0*1024.0))*8;
		System.out.println("BANDA DISPONIBILE RILEVATA (Mb/s) = "+banda);
		maxBandwithEstimated=false;
		
		return banda;
	}

	private DatagramPacket createPacket(double Min,double Max){
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		DataOutputStream daos = new DataOutputStream(baos);
		try {
			CurrentDispersion=(int)(Max+Min)/2;
			daos.writeInt(CurrentDispersion);
		} catch (IOException e) {

			e.printStackTrace();
		}
		
		DatagramPacket pak=new DatagramPacket(baos.toByteArray(),baos.toByteArray().length,serverHost,port);
		return pak;
	}
}
